.. _Вложенные функции, структуры и классы:

Вложенные функции, структуры и классы
======================================

.. index::
    вложенные функции

До сих пор мы определяли функции, структуры и классы в
внешних областях (то есть в области видимости модуля). Они могут быть определены и во внутренних областях
также. Определение их во внутренних областях помогает в инкапсуляции, сужая
видимость их символов, а также создает замыкания, которые мы рассматривали в разделе (:ref:`Указатели на функции, делегаты и лямбда-выражения<глава_72_start>`).
В качестве примера можно привести следующую функцию outerFunc(), содержащую определения
вложенной функции, вложенной структуры и вложенного класса:

.. code-block:: d

    void outerFunc(int parameter)
    {
        int local;
        void nestedFunc()
        {
            local = parameter * 2;
        }

        struct NestedStruct
        {
            void memberFunc()
            {
                local /= parameter;
            }
        }

        class NestedClass
        {
            void memberFunc()
            {
                local += parameter;
            }
        }
        // Using the nested definitions inside this scope:
        nestedFunc();
        auto s = NestedStruct();
        s.memberFunc();
        auto c = new NestedClass();
        c.memberFunc();
    }

    void main()
    {
        outerFunc(42);
    }

Как и любая другая переменная, вложенные определения могут обращаться к символам, которые определены в
их внешних диапазонах. Например, все три вложенных определения, приведенные выше, могут
использовать переменные с именами parameter и local.
Как обычно, имена вложенных определений действительны только в тех областях, в которых
в которых они определены. Например, nestedFunc(), NestedStruct и
NestedClass недоступны из main():abbr:

.. code-block:: d

    void main() {
        auto a = NestedStruct(); // ← ошибка компиляции
        auto b = outerFunc.NestedStruct(); // ← ошибка компиляции
    }

Хотя к их именам нельзя получить доступ, вложенные определения все равно можно использовать в
других областях. Например, многие алгоритмы Phobos решают свои задачи с помощью вложенных
структур, которые определяются внутри функций Phobos.
Чтобы увидеть пример этого, давайте разработаем функцию, которая потребляет фрагмент с
обоих концов поочередно:

.. code-block:: d

    import std.stdio;
    import std.array;

    auto alternatingEnds(T)(T[] slice)
    {
        bool isFromFront = true;
        struct EndAlternatingRange
        {
            bool empty() const
            {
                return slice.empty;
            }

            T front() const
            {
                return isFromFront ? slice.front : slice.back;
            }

            void popFront()
            {
                if (isFromFront)
                {
                    slice.popFront();
                    isFromFront = false;
                }
                else
                {
                    slice.popBack();
                    isFromFront = true;
                }
            }
        }

        return EndAlternatingRange();
    }

    void main()
    {
        auto a = alternatingEnds([1, 2, 3, 4, 5]);
        writeln(a);
    }

Несмотря на то, что вложенная структура не может быть упомянута внутри main(), ее все равно можно использовать:
[1, 5, 2, 4, 3]
Примечание: поскольку их имена не могут быть упомянуты вне области применения, такие типы называют
называются типами Волдеморта из-за аналогии с персонажем Гарри Поттера.
Обратите внимание, что вложенная структура, которую возвращает alternatingEnds(), не имеет
никаких переменных-членов. Эта структура справляется со своей задачей, используя только функцию
параметр slice и локальную переменную функции isFromFront. Тот факт, что
возвращаемый объект может безопасно использовать эти переменные даже после выхода из контекста, в котором
в котором он был создан, обусловлен автоматически созданным замыканием. Мы уже видели
замыкания в главе (:ref:`Указатели на функции, делегаты и лямбда-выражения<глава_72_start>`.

**static, когда замыкание не требуется**


Поскольку они сохраняют свои контексты живыми, вложенные определения стоят дороже, чем
чем их обычные аналоги. Кроме того, поскольку они должны содержать указатель контекста, чтобы
определить контекст, с которым они связаны, объекты вложенных определений
занимают больше места. Например, хотя следующие две структуры имеют
одинаковые переменные-члены, их размеры различны:


.. code-block:: d

    import std.stdio;

    struct ModuleStruct
    {
        int i;
        void memberFunc()
        {
        }
    }

    void moduleFunc()
    {
        struct NestedStruct
        {
            int i;
            void memberFunc()
            {
            }
        }

        writefln("OuterStruct: %s bytes, NestedStruct: %s bytes.",
            ModuleStruct.sizeof, NestedStruct.sizeof);
    }

    void main()
    {
        moduleFunc();
    }


Размеры этих двух структур могут отличаться в других средах:

.. code-block:: console

    OuterStruct: 4 байта, NestedStruct: 16 байт.


Однако некоторые вложенные определения нужны только для того, чтобы сохранить их как можно более локальными
без необходимости обращаться к переменным из внешних контекстов. В таких случаях
связанные с этим затраты будут излишними. Ключевое слово static удаляет
указатель контекста из вложенных определений, делая их эквивалентными своим
модульных аналогов. В результате статические вложенные определения не могут получить доступ к своим
внешним контекстам:

.. code-block:: d

    void outerFunc(int parameter)
    {
        static class NestedClass
        {
            int i;
            this()
            {
                i = parameter; // ← ошибка компиляции
            }
        }
    }

Контекстный указатель объекта вложенного класса доступен как void* через его свойство
.outer. Например, поскольку они определены в одной и той же области видимости, контекстные указатели следующих двух объектов
контекстные указатели следующих двух объектов равны:


.. code-block:: d

    void foo()
    {
        class C
        {
        }

        auto a = new C();
        auto b = new C();
        assert(a.outer is b.outer);
    }

.. index::
    вложенные классы

**Вложенные классы**


Когда класс вложен в другой класс, контекст, с которым связан вложенный объект.
ассоциируется с внешним объектом.
Такие вложенные классы создаются с помощью синтаксиса this.new. Когда это необходимо,
к внешнему объекту вложенного объекта можно обратиться с помощью this.outer

.. code-block:: d

    class OuterClass
    {
        int outerMember;
        class NestedClass
        {
            int func()
            {
                /* A nested class can access members of the outer
    * class. */
                return outerMember * 2;
            }

            OuterClass context()
            {
                /* A nested class can access its outer object
    * (i.e. its context) by '.outer'. */
                return this.outer;
            }
        }

        NestedClass algorithm()
        {
            /* An outer class can construct a nested object by
    * '.new'. */
            return this.new NestedClass();
        }
    }

    void main()
    {
        auto outerObject = new OuterClass();
        /* A member function of an outer class is returning a
    * nested object: */
        auto nestedObject = outerObject.algorithm();
        /* The nested object gets used in the program: */
        nestedObject.func();
        /* Naturally, the context of nestedObject is the same as
    * outerObject: */
        assert(nestedObject.context() is outerObject);
    }

Вместо this.new и this.outer, .new и .outer можно также использовать для существующих:

.. code-block:: d

    auto var = new OuterClass();
    auto nestedObject = var.new OuterClass.NestedClass();
    auto var2 = nestedObject.outer;

Резюме
-------

* Функции, структуры и классы, определенные во внутренних диапазонах, могут обращаться к этим диапазонам как к своим контекстам.
  к этим областям как к своим контекстам.
* Вложенные определения сохраняют свои контексты для формирования замыканий.
* Вложенные определения более дорогостоящие, чем их аналоги в модулях. Когда
  вложенное определение не нуждается в доступе к своему контексту, этих затрат можно избежать
  с помощью ключевого слова static.
* Классы могут быть вложены внутрь других классов. Контекстом такого вложенного объекта
  является сам внешний объект. Объекты вложенных классов создаются с помощью this.new или
  variable.new, а их контексты доступны по this.outer или variable.outer
